// The dialect package provides an abstraction layer around the idiosyncracies
// of various dialects of microcomputer BASIC so that other parts of Microtool
// don't require special exceptions.
package dialect

import (
	"code.google.com/p/microtool/util/stringset"
)

// data expresses the static information for a dialect.  The Dialect type embeds
// this data and provides an external interface.
type data struct {
	// Name identifies a dialect for the --dialect flag.
	Name string

	// Info is a brief human-readable description of the dialect.
	Info string

	// Keywords contains all the keywords used by this dialect of BASIC.  If
	// a single keyword has multiple forms with the same tokenization, the
	// desired display form should be specified last.
	Keywords []*Keyword
}

// buildNameMap builds up a map from string to completions.  When tokenizing
// code builds up a value that matches an entry in this map, it must do a
// lookahead to see if a longer keyword should be matched instead.
func (d *data) buildNameMap() map[string]*Completion {
	// First pass: Just populate the map without reference to suffixes.
	names := make(map[string]*Completion)
	for _, kw := range d.Keywords {
		names[kw.Name] = &Completion{Keyword: kw}
	}

	// Second pass: For each keyword, make sure that matching prefixes point to it.
	for name := range names {
		for i := 1; i < len(name); i++ {
			if prefix, ok := names[name[:i]]; ok {
				if prefix.Suffixes == nil {
					prefix.Suffixes = stringset.NewSet()
				}
				prefix.Suffixes.Add(name[i:])
			}
		}
	}
	return names
}

// Keyword contains the static information for an individual keyword.  Besides
// reserved words that look like identifiers, dialects should also include an
// entry for every token with its own encoding (<, >, etc.).
type Keyword struct {
	// Name is the string that actually matches this keyword in ASCII source
	// text.
	Name string

	// The byte encoding for this keyword in native storage format.
	Encoding []byte
}

// A Completion is used to record possible keyword completion of an identifier
// or binary token in progress.  This is used to work around an oddity of many
// old BASIC interpreters: whitespace is optional and identifiers are captured
// greedily, yet some keywords are prefixes of others.  Ugh.
type Completion struct {
	Keyword  *Keyword
	Suffixes stringset.Set
}

// A Dialect encapsulates the information needed to gloss over differences among
// various BASIC dialects.  All client code should prefer extending the
// functionality of this type to implementing dialect-specific special cases
// elsewhere.
type Dialect struct {
	data   *data
	names  map[string]*Completion
	tokens map[byte]*Completion
}

// Info returns the brief description of this dialect.
func (d *Dialect) Info() string {
	return d.data.Info
}

// Name returns the name of this dialect.
func (d *Dialect) Name() string {
	return d.data.Name
}

// MatchKeyword checks to see if this string matches a keyword in the dialect.
// If it does, it returns the Completion.
func (d *Dialect) MatchKeyword(s string) *Completion {
	return d.names[s]
}

// The Active map holds all currently available dialects.
var Active = map[string]*Dialect{
	gwbasic.Name: newDialect(gwbasic),
	trs80level2.Name: newDialect(trs80level2),
}

// newDialect constructs a new Dialect from the given dialect data and populates
// all of the internal derived data structures.
func newDialect(data *data) *Dialect {
	return &Dialect{
		data:   data,
		names:  data.buildNameMap(),
		// TODO: Populate the token completion map -- not necessary until
		// Microtool supports reading tokenized input.
		//tokens: data.buildTokenMap(),
	}
}
